S16-00 专题-JS-手写
[TOC]
手写-call/aplly/bind
- function.call():
(thisArg,arg1?,arg2?,...)
,用于显式调用一个函数,并动态指定函数执行时的 this 值及参数列表。 - function.apply():
(thisArg,args?)
,用于显式调用一个函数,并动态指定函数执行时的 this 值及参数列表。 - function.bind():
(thisArg,arg1?,arg2?,...)
,用于创建一个新的函数,该函数在调用时会以指定的 this 值和预先提供的参数作为默认参数。
函数对象原型关系
函数 foo 对象的隐式原型 === Function 的显式原型
// 函数foo对象的隐式原型 === Function的显式原型
console.log(foo.__proto__ === Function.prototype); // true
console.log(Function.prototype.apply); // f apply()
console.log(Function.prototype.call); // f call()
console.log(Function.prototype.bind); // f bind()
console.log(Function.prototype.apply === foo.apply); // true
结论:
- foo 对象中的某些属性和方法是来自
Function.prototype
的。 - 在
Function.prototype
中添加的属性和方法,可以被所有的函数获取。
在 Function 的原型中添加方法 bar
手写 apply
前置知识:
给函数对象添加方法:可以通过在Function.prototype上添加方法实现。
apply/call使用示例:
基本实现
apply实现思路:
- 将函数设置为目标对象的属性:
mrthis.fn = this
- 执行该函数:
mrthis.fn(...args)
- 删除添加的属性:
delete mrthis.fn
- 返回函数执行结果
function foo() {
console.log("foo", this);
}
Function.prototype.mrapply = function (mrthis) {
// 相当于 mrthis.fn = this
Object.defineProperty(mrthis, "fn", {
configurable: true,
value: this,
});
// 隐式调用fn,可以让fn函数的this指向 mrthis
mrthis.fn(); // 相当于 this()
// 删除多出来的临时函数fn
delete mrthis.fn;
};
foo.mrapply({ name: "Tom" });
优化:this类型处理
this类型处理:
如果传入的this参数是一个 String 或者 Number 的类型,需要将其包裹成对象类型,才能在它上面添加属性
如果传入的this参数是undefined或null,this指向window
function foo (age, height) {
console.log('foo', this, age, height)
}
Function.prototype.mrapply = function(mrthis) {
// 当this不是对象时,需要用Object包裹
mrthis = (mrthis === null || mrthis === undefined) ? window : Object(mrthis)
// 相当于 mrthis.fn = this
Object.defineProperty(mrthis, 'fn', {
configurable: true,
value: this
})
// 隐式调用fn,可以让fn函数的this指向 mrthis
mrthis.fn()
// 删除多出来的临时函数fn
delete mrthis.fn
}
foo.mrapply({name: "Tom"})
foo.mrapply(null)
foo.mrapply(undefined)
foo.mrapply(true)
foo.mrapply(123)
foo.mrapply('aaaa')
优化:绑定参数
调用 mrapply 时,传递参数
function foo (age, height) {
console.log('foo', this, age, height)
}
Function.prototype.mrapply = function(mrthis, args) {
// 当this不是对象时,需要用Object包裹
mrthis = (mrthis === null || mrthis === undefined) ? window : Object(mrthis)
// 相当于 mrthis.fn = this
Object.defineProperty(mrthis, 'fn', {
configurable: true,
value: this
})
// 隐式调用fn,可以让fn函数的this指向 mrthis
mrthis.fn(...args)
// 删除多出来的临时函数fn
delete mrthis.fn
}
foo.mrapply({name: "Tom"}, [18, 1.88])
foo.mrapply(null, [18, 1.88])
foo.mrapply(undefined, [18, 1.88])
foo.mrapply(true, [18, 1.88])
foo.mrapply(123, [18, 1.88])
foo.mrapply('aaaa', [18, 1.88])
手写 call
实现思路:与 call()
类似,但处理参数为数组形式。
function foo(age, height) {
console.log('foo', this, age, height)
}
Function.prototype.mrcall = function(mrthis, ...args) { // 区别:apply:(mrthis, args)
mrthis = (mrthis === null || mrthis === undefined) ? window : Object(mrthis)
Object.defineProperty(mrthis, 'fn', {
configurable: true,
value: this
})
mrthis.fn(...args)
delete mrthis.fn
}
foo.mrcall({ name: "张飞" }, 20, 1.77)
抽取封装公共函数
抽取封装的函数
js/* 抽取封装的函数 */ Function.prototype.mrexec = function(mrthis, args) { mrthis = (mrthis === null || mrthis === undefined) ? window : Object(mrthis) // mrthis.fn = this Object.defineProperty(mrthis, 'fn', { configurable: true, value: this }) mrthis.fn(...args) delete mrthis.fn }
手写apply
js/* 手写apply */ Function.prototype.mrapply = function(mrthis, args) { this.mrexec(mrthis, args) }
手写call
js/* 手写call */ Function.prototype.mrcall = function(mrthis, ...args) { this.mrexec(mrthis, args) }
测试
js// 测试 function foo(age, height) { console.log('foo', this, age, height) } foo.mrapply({name: "Tom"}, [19, 1.66]) foo.mrcall({name: "Jack"}, 22, 1.99)
手写 bind
bind使用示例:和apply/call不同,bind执行后是返回一个新的函数newFoo。
基本实现
思路:想办法实现如下:
// 伪代码
{ name: "why" }.foo(name, age)
/* 手写bind */
Function.prototype.mrbind = function(mrthis, ...args) {
return (...moreArgs) => {
mrthis = (mrthis === null || mrthis === undefined) ? window : Object(mrthis)
Object.defineProperty(mrthis, 'fn', {
configurable: true,
value: this
})
// 合并2个方法中传递的参数
const allArgs = [...args, ...moreArgs]
mrthis.fn(...allArgs) // 相当于 this()
delete mrthis.fn // 可以删除fn,因为每次调用newFoo,都会重新生成一个mrthis.fn
}
}
// 测试
function foo(name, age, height, address) {
console.log('foo', this, name, age, height, address)
}
const newFoo = foo.mrbind({name: "Jerry"}, '张飞', 45)
console.log(newFoo)
newFoo(1.88, '成都')
newFoo(1.88, '成都')
手写-防抖/节流
概述
防抖和节流的概念其实最早并不是出现在软件工程中,防抖是出现在电子元件中,节流出现在流体流动中
而JavaScript是事件驱动的,大量的操作会触发事件,加入到事件队列中处理。
而对于某些频繁的事件处理会造成性能的损耗,我们就可以通过防抖和节流来限制事件频繁的发生;
防抖和节流函数目前已经是前端实际开发中两个非常重要的函数,也是面试经常被问到的面试题。
但是很多前端开发者面对这两个功能,有点摸不着头脑:
某些开发者根本无法区分防抖和节流有什么区别(面试经常会被问到);
某些开发者可以区分,但是不知道如何应用;
某些开发者会通过一些第三方库来使用,但是不知道内部原理,更不会编写;
接下来我们会一起来学习防抖和节流函数:
我们不仅仅要区分清楚防抖和节流两者的区别,也要明白在实际工作中哪些场景会用到;
并且我会带着大家一点点来编写一个自己的防抖和节流的函数,不仅理解原理,也学会自己来编写;
防抖
防抖(Debounce):是一种前端性能优化技术,用于限制高频触发事件的回调函数执行次数。其核心思想是:事件触发后,等待一段时间再执行回调。若在等待期间事件再次触发,则重新计时,直到等待期结束后才真正执行一次回调。
类比理解(电梯场景):
想象你在电梯门口:
- 有人进电梯(触发事件)→ 电梯门开始关闭(开始计时)。
- 若在关门期间又有人进来(再次触发) → 电梯门重新打开(重置计时)。
- 直到连续
N
秒无人进入(等待期结束) → 电梯门关闭并运行(执行一次回调)。
图示理解:
我们用一副图来理解一下它的过程:
当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间;
当事件密集触发时,函数的触发会被频繁的推迟;
只有等待了一段时间也没有事件触发,才会真正的执行响应函数;
应用场景:
防抖的应用场景很多:
搜索联想:
oninput
,输入框中频繁的输入内容,搜索或者提交信息;频繁点击按钮:
onclick
,频繁的点击按钮,触发某个事件;浏览器滚动事件:
onscroll
,监听浏览器滚动事件,完成某些特定操作;浏览器缩放事件:
onresize
,用户缩放浏览器的resize事件;
示例:搜索联想 :
我们都遇到过这样的场景,在某个搜索框中输入自己想要搜索的内容:
比如想要搜索一个MacBook:
当我输入m时,为了更好的用户体验,通常会出现对应的联想内容,这些联想内容通常是保存在服务器的,所以需要一次网络请求;
当继续输入ma时,再次发送网络请求;
那么macbook一共需要发送7次网络请求;
这大大损耗我们整个系统的性能,无论是前端的事件处理,还是对于服务器的压力;
但是我们需要这么多次的网络请求吗?
不需要,正确的做法应该是在合适的情况下再发送网络请求;
比如如果用户快速的输入一个macbook,那么只是发送一次网络请求;
比如如果用户是输入一个m想了一会儿,这个时候m确实应该发送一次网络请求;
也就是我们应该监听用户在某个时间,比如500ms内,没有再次触发时间时,再发送网络请求;
这就是防抖的操作:只有在某个时间内,没有再次触发某个函数时,才真正的调用这个函数;
我们通过一个搜索框来延迟防抖函数的实现过程:
- 监听input的输入,通过打印模拟网络请求
测试发现快速输入一个macbook共发送了7次请求,显示我们需要对它进行防抖操作:
生活中防抖的例子:
比如说有一天我上完课,我说大家有什么问题来问我,我会等待五分钟的时间。
如果在五分钟的时间内,没有同学问我问题,那么我就下课了;
在此期间,a同学过来问问题,并且帮他解答,解答完后,我会再次等待五分钟的时间看有没有其他同学问问题;
如果我等待超过了5分钟,就点击了下课(才真正执行这个时间);
节流
节流(throttle)
我们用一副图来理解一下节流的过程
当事件触发时,会执行这个事件的响应函数;
如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行函数;
不管在这个中间有多少次触发这个事件,执行函数的频率总是固定的;
应用场景:
页面滚动事件:监听页面的滚动事件;
鼠标移动事件;
频繁点击事件:用户频繁点击按钮操作;
游戏某些设计:游戏中的一些设计,如发射子弹;
很多人都玩过类似于飞机大战的游戏
在飞机大战的游戏中,我们按下空格会发射一个子弹:
很多飞机大战的游戏中会有这样的设定,即使按下的频率非常快,子弹也会保持一定的频率来发射;
比如1秒钟只能发射一次,即使用户在这1秒钟按下了10次,子弹会保持发射一颗的频率来发射;
但是事件是触发了10次的,响应的函数只触发了一次;
生活中节流的例子:
比如说有一天我上完课,我说大家有什么问题来问我,但是在一个5分钟之内,不管有多少同学来问问题,我只会解答一个问题;
如果在解答完一个问题后,5分钟之后还没有同学问问题,那么就下课;
手写防抖
我们按照如下思路来实现:
- 防抖基本功能实现:可以实现防抖效果
- 优化一:优化参数和this指向
- 优化二:优化取消操作(增加取消功能)
- 优化三:优化立即执行效果(第一次立即执行)
- 优化四:优化返回值
基本实现
优化:参数和this绑定
this指向
参数
优化:取消功能
优化:第一次立即执行
immediate
:控制否时启用立即执行功能isInvoke
:控制函数是否已经立即执行一次了
优化:返回值
手写节流
我们按照如下思路来实现:
- 节流函数的基本实现:可以实现节流效果
- 优化一:绑定this和参数
- 优化二:控制立即执行,节流最后一次也可以执行
- 优化三:优化添加取消功能
- 优化四:优化返回值问题
基本实现
优化:绑定this和参数
优化:控制立即执行
优化:控制执行最后一次
思路一: 给每次点击时添加一个定时器,延迟时间设为waitTime,当再次点击时取消上次的定时器,重新添加一个。
思路二: 在每个执行fn函数的节点,添加一个延迟时间为waitTime的定时器,当用户在fn函数执行节点的时间上也点击了一次就取消该定时器(使用中)
优化:取消功能
优化:返回值
手写-深拷贝函数
浅拷贝/深拷贝
对象相互赋值:
前面我们已经学习了对象相互赋值的一些关系,分别包括:
const info = {
name: 'tom',
age: 18,
friend: {
name: 'jack'
}
}
引用赋值:指向同一个对象,相互之间会影响;
jsconst obj1 = info // 特性:obj1 和 info 指向同一个对象
对象的浅拷贝:只是浅层的拷贝,内部引入对象时,依然会相互影响;
jsconst obj2 = {...info} // 方式一: const obj3 = Object.assign({}, info) // 方式二: // 特性1:修改 obj2.name,原对象 info.name 的值依然是 'tom' obj2.name = '张飞' console.log(info.name) // 'tom' // 特性2:修改 obj2.friend.name,原对象 info.friend.name 的值会随之改变成 '曹操' obj2.friend.name = '曹操' console.log(info.friend.name) // '曹操'
对象的深拷贝:两个对象不再有任何关系,不会相互影响;
深拷贝实现方式:
JSON.parse
jsconst obj = JSON.parse(JSON.stringify(info))
第三方库:underscore、lodash
自己实现
JSON.parse
的缺点:
前面我们已经可以通过一种方法来实现深拷贝了:JSON.parse
这种深拷贝的方式其实对于函数、Symbol等是无法处理的;
并且如果存在对象的循环引用,也会报错的;
手写深拷贝
1.自定义深拷贝的基本功能;
2.对Symbol的key进行处理;
3.其他数据类型的值进程处理:数组、函数、Symbol、Set、Map;
4.对循环引用的处理;
工具函数:判断对象
实现思路:通过 typeof
判断,并对 typeof
返回的结果进行分析,找出是对象的类型返回。
基本实现
优化:区分数组和对象
实现思路:通过 Array.isArray() 区分是数组还是对象。
优化:其他类型-Set
问题:当对象中有Set类型时,无法拷贝。
分析:这是因为 deepCopy() 内部是通过对目标对象执行 for...in 操作进行遍历的,而Set通过for...in操作返回的是空值。
优化:其他类型-Map
思路和Set一致
优化:其他类型-Function
function: 不需要深拷贝
优化:其他类型-Symbol为值
问题:当对象中有Symbol类型的值时,会直接返回原始的Symbol值,此处的新值和旧值指向同一个Symbol。
优化:其他类型-Symbol为key
问题:Symbol作为key时,无法通过 for...in
遍历出来。
解决:必须对Symbol类型的key单独使用 Object.getOwnPropertySymbols()
,再通过 for...of
遍历。
优化:处理循环引用
循环引用问题:当原始对象出现循环引用时,调用 deepCopy()
会出现无限递归,最终报错:
方案一:将每次新创建的对象保存到Map中,每次遍历前判断之前是否已经保存过了该对象。
问题:需要在deeCopy外部定义一个map,并且每次拷贝完成后map依然会形成对对象的强引用,没有销毁。
方案二(推荐):使用WeakMap替代Map;将map放入参数中并设置一个默认值new WeakMap()
手写-事件总线
事件总线
- mitt():
(all?)
,用于创建一个事件总线实例,支持事件的监听、触发和移除。 - emitter.on():
(type,handler)
,用于 监听指定类型的事件,当该事件被触发时,执行绑定的处理函数。支持监听具体事件类型或使用通配符*
监听所有事件。 - emitter.off():
(type,handler?)
,用于 移除指定事件类型的监听器,支持移除单个处理函数或清空某事件类型的所有监听器,避免内存泄。 - emitter.emit():
(type,data?)
,用于 触发指定类型的事件,并传递相关数据给所有监听该事件的处理器。 - emitter.all:
{eventName: Handler[]}
,用于存储所有已注册的事件类型及其对应的事件处理器列表,用于调试或高级操作。
自定义事件总线属于一种观察者模式,其中包括三个角色:
发布者(Publisher):发出事件(Event);
订阅者(Subscriber):订阅事件(Event),并且会进行响应(Handler);
事件总线(EventBus):无论是发布者还是订阅者都是通过事件总线作为中台的;
当然我们可以选择一些第三方库:
Vue2默认是带有事件总线的功能;
Vue3中推荐一些第三方库,比如mitt;
当然我们也可以实现自己的事件总线:
事件的监听方法on;
事件的发射方法emit;
事件的取消监听off;
手写事件总线
基本实现
优化:绑定参数
优化:移除监听
实现思路:
- 遍历
eventMap
中事件名对应的事件函数数组,找到需要删除的事件函数,将其删除。 - 如果事件名对应的事件函数数组已经为空,则删除该事件名属性。
完整代码
手写-Promise
Promise结构设计
Promses/A+ 规范: https://promisesaplus.com/
Promise三种状态
调用回调函数时传递参数
实例方法
then
then基本实现
思路:
1 then接收
onFulfilled
和onRejected
参数,并将其保存到this上2.1 在resolve()回调方法的
queueMicrotask()
回调函数参数中调用onFulfilled
方法2.2 在reject()回调方法的
queueMicrotask()
回调函数参数中调用onRejected
方法
注意: queueMicrotask(cb)
方法的作用是将cb回调方法加入到微任务队列中。
同一个promise多次调用then
思路: 将需要多次调用的成功回调和失败回调分别放入一个数组中,调用时再遍历该数组,分别调用数组中的回调方法
1、定义2个数组,将then中的成功、失败回调分别push到这2个数组中
2、遍历这2个数组,再分别调用数组中的回调方法
异步延时调用then
问题: then方法如果在延迟1秒后调用,当promise的resolve()执行时,该then方法的回调函数不会被执行。
分析: 这是因为当promise内部的resolve()执行时,then方法由于延迟原因还没有加入到数组onFulfilledFns
中,也就不会被执行。
解决: 可以在then方法中,事先判断promise的状态:
- 如果已经是
fulfilled
或rejected
,表示已经执行了resolve()
或rejecct()
方法,此时可以直接调用延迟调用的then方法的回调函数。 - 只有在
pending
状态才将then方法的回调函数push到数组中保存。
问题: 此时res1、res2无法接收到resolve()执行后的参数
分析: 这是因为执行了resolve()方法后,status立马变成fulfilled
,再执行then()方法时status已经处于fulfilled
状态,then中的回调会被直接调用,此时queueMicrotask()
方法还没有执行,this.value
还没有赋值。
解决: 将状态status放入微队列queueMicrotask中
问题: 将状态status放入微队列queueMicrotask中后,resolve和reject都会执行,加入微任务队列
分析: 这是由于resolve()和reject()在加入微任务时,status的状态都为pending。因此都会被加入微任务队列。
解决: 在加入微任务前判断当前状态是否为pending,如果不是pending则表示已经执行了某个回调,就不能加入微任务
then方法的链式调用
思路:
- 当前then方法没有返回值,所以默认会返回undefined,不能通过undefined.then()链式调用方法。
- 通过then方法中返回一个新的Promise,可以实现链式调用then方法
- 新Promise中resolve(res)或reject(err)的参数res或err必须是上一次then中回调返回的结果
问题: 在new Promise中抛出异常的情况
解决: 在constructor中捕获执行executor()
的异常。
封装try catch中相似的代码
then回调函数参数可选【
then执行结果值类型【
判断下面result的类型:普通值、promise、thenable
补充:
- 可以通过
result instanceof Promise
判断否是一个Promise - 可以通过
typeof result.then === 'function'
判断是否是一个thenable对象
catch
调用catch方法
▸ 基本实现
思路:通过调用then方法时只传递reject回调实现catch
问题: 在回调函数有值(存在)的情况下,才去执行函数或添加到数组中
问题: 调用catch的是返回的新promise,不是和then同一个promise
解决: 当promise1中的reject为undefined
时,在then方法执行reject回调处抛出一个异常。这样就会被第二个promise接收到了
finally
调用finally方法
▸ 基本实现
思路: 可以借用then()方法,在then方法的resolve和reject回调中都调用onfinally()
实现
问题: 添加catch后,执行resolve时,finally被阻止了,不再执行finally中的回调。只有执行reject时才会执行finally
原因: 这是由于catch方法中是这样调用then的:this.then(undefined, onRejected)
,其中成功回调是undefined
,所以就不会处理上次then返回的值
解决: 当onFulfilled为undefined时,给它一个默认的回调函数:value => { return value }
类方法
resolve
思路: 直接在 new Promise()
中调用 resolve()
方法
使用resolve
reject
思路: 直接在 new Promise()
中调用 reject()
方法
使用reject
all
特点:
- all中所有的promise都有结果后才会执行then或catch方法
- 所有的promise之间执行与运算:都为resolve进入then方法;有一个reject进入catch方法
关键: 什么时候要执行resolve、什么时候要执行reject
思路: 遍历all的promises参数
- 当有一个promise结果为reject时直接执行reject(),
- 否则进入then,并保存结果到values中,当所有promise都有结果并且结果为resolve时,执行resolve()
使用all
allSettled
特性: allSettled会等所有promise都有结果(不区分resolve和reject),进入then方法,不会进入catch方法
使用allSettled
race
特性: 只要有一个promise有结果,race立马有结果,无论resolve还是reject
等价于下面的写法:
使用race
any
特性:
- 必须等到promise有一个resolve的结果,any才会有一个resolve的结果
- 否则必须等到所有的promise都为reject结果,any才会有一个reject的结果
使用any
最终代码@
/* 工具函数-封装try...catch函数 */
function runFunctionWithCatchError(fn, value, resolve, reject) {
try {
resolve(fn(value))
} catch (err) {
reject(err)
}
}
// Promise状态
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'
class MrPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING
this.value = undefined
this.reason = undefined
this.onFulfilledFns = []
this.onRejectedFns = []
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_FULFILLED
this.value = value
for (const fn of this.onFulfilledFns) {
fn(this.value)
}
})
}
}
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return
this.status = PROMISE_STATUS_REJECTED
this.reason = reason
for (const fn of this.onRejectedFns) {
fn(this.reason)
}
})
}
}
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
// 判断onFulfilled、onRejected回调函数是否存在
onRejected = onRejected || ((err) => { throw err })
onFulfilled = onFulfilled || ((res) => res)
return new MrPromise((resolve, reject) => {
// console.log('then status: ', this.status)
if (this.status === PROMISE_STATUS_FULFILLED) {
runFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
}
if (this.status === PROMISE_STATUS_REJECTED) {
runFunctionWithCatchError(onRejected, this.reason, resolve, reject)
}
if (this.status === PROMISE_STATUS_PENDING) {
this.onFulfilledFns.push(() => {
runFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
})
this.onRejectedFns.push(() => {
runFunctionWithCatchError(onRejected, this.reason, resolve, reject)
})
}
})
}
catch(onRejected) {
return this.then(undefined, onRejected)
}
finally(onFinally) {
this.then(
() => {
onFinally()
},
() => {
onFinally()
}
)
}
static resolve(value) {
return new Promise((resolve) => resolve(value))
}
static reject(reason) {
return new Promise((resolve, reject) => reject(reason))
}
static all(promises) {
return new Promise((resolve, reject) => {
const values = []
promises.forEach((promise) => {
promise.then(
(res) => {
values.push(res)
if (values.length === promises.length) {
resolve(values)
}
},
(err) => {
reject(err)
}
)
})
})
}
static allSettled(promises) {
return new Promise((resolve, reject) => {
const results = []
promises.forEach((promise) => {
promise.then(
(res) => {
results.push({ status: 'fulfilled', value: res })
if (results.length === promises.length) {
resolve(results)
}
},
(err) => {
results.push({ status: 'rejected', reason: err })
if (results.length === promises.length) {
resolve(results)
}
}
)
})
})
}
static race(promises) {
return new Promise((resolve, reject) => {
promises.forEach((promise) => {
promise.then(
(res) => {
resolve(res)
},
(err) => {
reject(err)
}
)
})
})
}
static any(promises) {
return new Promise((resolve, reject) => {
const reasons = []
promises.forEach((promise) => {
promise.then(
(res) => {
resolve(res)
},
(err) => {
reasons.push(err)
if (reasons.length === promises.length) {
reject(new AggregateError(err))
}
}
)
})
})
}
}
测试
// 测试
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p1~')
}, 3000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p2~')
}, 5000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p3~')
}, 3000)
})
const p = new MrPromise((resolve, reject) => {
// throw new Error('抛出异常')
resolve('aaa')
// reject('111')
// setTimeout(() => {
// // resolve('aaa')
// reject('111')
// }, 1000)
})
// - 类方法-any
Promise.any([p1, p2, p3]).then(
(res) => {
console.log('any res: ', res)
},
(err) => {
console.log('any err: ', err)
}
)
// // - 类方法-race
// Promise.race([p1, p2, p3]).then(
// (res) => {
// console.log('race res: ', res)
// },
// (err) => {
// console.log('race err: ', err)
// }
// )
// // - 类方法-allSettled
// Promise.allSettled([p1, p2, p3]).then((res) => {
// console.log('allSettled: ', res)
// })
// // - 类方法-all
// Promise.all([p1, p2, p3]).then(
// (res) => {
// console.log('all res: ', res)
// },
// (err) => {
// console.log('all err: ', err)
// }
// )
// // - 类方法-reject
// Promise.reject('222').catch((err) => {
// console.log(err)
// })
// // - 类方法-resolve
// Promise.resolve('1111').then((res) => {
// console.log(res)
// })
// p.then(
// (res) => {
// console.log('res: ', res)
// },
// (err) => {
// console.log('err: ', err)
// }
// )
// // - 异步延迟调用
// setTimeout(() => {
// p.then(
// (res) => {
// console.log('异步延时调用 res: ', res)
// },
// (err) => {
// console.log('异步延时调用 err: ', err)
// }
// )
// }, 2000)
// // - 链式调用
// p.then(
// (res) => {
// console.log('链式调用 res1: ', res)
// return 'bbb'
// },
// (err) => {
// console.log('链式调用 err1: ', err)
// return '222'
// }
// ).then(
// (res) => {
// console.log('链式调用 res2: ', res)
// return 'ccc'
// },
// (err) => {
// console.log('链式调用 err2: ', err)
// return '333'
// }
// )
// // - catch
// p.then((res) => {
// console.log('then res: ', res)
// }).catch((err) => {
// console.log('catch err: ', err)
// })
// // - finally
// p.then((res) => {
// console.log('then res: ', res)
// })
// .catch((err) => {
// console.log('catch err: ', err)
// })
// .finally(() => {
// console.log('finally~')
// })
// p.then(
// (res) => {
// console.log('res: ', res)
// },
// (err) => {
// console.log('err: ', err)
// }
// )
// p.then(
// (res) => {
// console.log('res2: ', res)
// },
// (err) => {
// console.log('err2: ', err)
// }
// )